home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / mmdf / awkmail next >
Encoding:
AWK Script  |  1997-08-26  |  54.3 KB  |  1,557 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) awkmail.gawk 2.1 97/07/31
  3. # 91/03/13 john h. dubois iii (john@armory.com)
  4. # 91/03/30 converted from a collection of ksh & awk programs
  5. #          to ksh preliminaries & one awk program
  6. # 91/10/28 fixed incompatibilities with gawk
  7. #          fixed bugs in parsing of user@machine, etc.
  8. #          added alias expansion
  9. #          changed to separate ksh & awk scripts when awk part got too big
  10. # 92/05/01 Changed to #!gawk script.
  11. # 93/12/09 Send local mail through rmail, not execmail, for more useful test.
  12. #          Include mail to local uucp name or local domain names as local mail.
  13. #          Added f option.
  14. # 93/12/13 Optimized so that mail that will only be fed to one process is not
  15. #          stored, which can take a very long time in awk; it is read & 
  16. #          written to process.
  17. #          Use vales of MAILRC and MUSHRC.
  18. #          Put addressees on To: line.
  19. # 94/07/27 Added r option.
  20. # 96/03/18 Added b option.
  21. # 96/06/03 Added nix options; removed V option.
  22. # 96/09/10 Fixed x option and MUSHRC env var handling.  Added t option.
  23. # 97/02/03 Added T option.
  24. # 97/02/09 Added V and L options.
  25. # 97/02/16 Added auk options.  Use mail-send lib.
  26. # 97/02/18 Added o option.
  27. # 97/02/22 Added M option.
  28. # 97/02/26 Added Q option.
  29. # 97/06/08 Add X-Mailer field.
  30.  
  31. # This program is used for debugging mail problems.
  32.  
  33. BEGIN {
  34.  
  35.     Name = "awkmail"
  36.     Usage = "Usage:\n"\
  37. Name " -[abu] [-hiqQrtTw] [-V<log-level>] [-s<subject>] [-x<debug-level>]\n"\
  38. "        [-S<sender>] [-k<seconds>] [-L<logfile>] [-o<submit-opts>]\n"\
  39. "        [-M<from>] recipient-addr ..."
  40.     ARGC = Opts(Name,Usage,"abiqs:hrS;ux>tTV;wL;k>o:M:Q",1)
  41.     if ((Err = ExclusiveOptions("b,u,r;x,Q",Options)) != "") {
  42.     printf "Error: %s\n",Err > "/dev/stderr"
  43.     Err = 1
  44.     exit(1)
  45.     }
  46.     TestMailMessage = "This is a test message."
  47.     # the -L option does not appear to work; submit recognizes it and says it
  48.     # has set the logfile to the given name, but still logs to default
  49. #"-L<logfile>: Have submit log to the file <logfile> instead of the logfile\n"\
  50. #"    specified in mmdftailor, or the default of /usr/mmdf/log/chan.log.  Use\n"\
  51. #"    -L/dev/tty to display the information instead of writing it to a file.\n"\
  52. #"    As with -V, -L can only be used by root or mmdf.\n"\
  53.     if ("h" in Options) {
  54.     printf \
  55. "%s: send mail by various MTAs.\n"\
  56. "%s\n"\
  57. "Recipients is a list of user addresses to which a message is to be sent.\n"\
  58. "Addresses must be of the form user@site, site![site! ... ]user, or user.\n"\
  59. "site must be known to the uucp system of this system.\n"\
  60. "In the last case, user must be a user on this system.\n"\
  61. "Alias expansion from .mailrc is done.\n"\
  62. "If no subject is given on the command line, a subject will be asked for.\n"\
  63. "Therefore, if %s is used non-interactively, a subject should always\n"\
  64. "be given on the command line.\n"\
  65. "Mail is sent to local recipients through rmail, and to remote recipients\n"\
  66. "through uux. \n"\
  67. "Options:\n"\
  68. "One of the following three options must be used:\n"\
  69. "-b: Send mail to all recipients via submit.\n"\
  70. "-r: Send mail to all recipients via rmail.\n"\
  71. "-u: Send mail to local recipients via rmail, and remote recipients via uux.\n"\
  72. "Other options:\n"\
  73. "-a: Do not do alias expansion using .mailrc file.\n"\
  74. "-s<subject>: Set the mail subject.\n"\
  75. "-h: Print this help.\n"\
  76. "-S<sender>: Set the address the mail is from for both the From_ and From:\n"\
  77. "    lines.\n"\
  78. "-M<from>: Sets the From: address only; overrides -S for that purpose.\n"\
  79. "-x<level>: Set debugging to <level>, which should be a positive integer.\n"\
  80. "    Higher levels give more diagnostics.\n"\
  81. "-T: Generate test mail.  This prevents %s from reading the body of the\n"\
  82. "    message from the input; instead it is set to \"%s\"\n"\
  83. "    If -s is not given, the subject is set to \"Test Message\".\n"\
  84. "-Q: Quiet operation.  Do not print anything except error messages.  In\n"\
  85. "    particular, when -s is used, the subject is not echoed to the output.\n"\
  86. "The following options can only be used in conjunction with -b:\n"\
  87. "-t: Trust the author identification.  This will cause a Source-Info: field\n"\
  88. "    to be added to the header if the sender identity does not appear\n"\
  89. "    correct (without -t, submit will fail in that circumstance).\n"\
  90. "-q: Don't return undeliverable mail.  Remote systems may still return mail.\n"\
  91. "-w: Watch submission progress and immediate deliver attempts.\n"\
  92. "-V<log-level>: Set the logging level to <log-level>, which should be one\n"\
  93. "    of the MMDF log levels.  These may also be referred to as 1 through 8:\n"\
  94. "    FAT logs fatal errors only. TMP logs temporary errors and fatal errors.\n"\
  95. "    GEN logs generally interesting diagnostics.  BST logs basic statistics.\n"\
  96. "    FST gives full statistics.  PTR gives program trace listing.\n"\
  97. "    BTR give more detail tracing.  FTR gives every possible diagnostic.\n"\
  98. "    This option can only be used by root or MMDF.  Attempts to use it by\n"\
  99. "    non-privileged users will result in submit giving an error, usually\n"\
  100. "    'Invalid parameter character'.\n"\
  101. "-k<seconds>: Specify nameserver timeout period.\n"\
  102. "-i: Deliver mail immediately, regardless of MMDF configuration.\n"\
  103. "-o<submit-opts>: Set options to be passed to submit.  Multiple -o options\n"\
  104. "    may be given.  Multiple flag options may be given together, as a\n"\
  105. "    string.  Options that take a value must be given by themselves, with\n"\
  106. "    the value following the option letter in the same argument.\n"\
  107. "    Examples: -ouvc -ok10\n",
  108.     Name,Usage,Name,Name,TestMailMessage
  109.     exit(0)
  110.     }
  111.     if ("x" in Options) {
  112.     Debug = Options["x"]
  113.     printf "Debugging level set to %s\n",Debug > "/dev/stderr"
  114.     printf "%d args left in ARGV:\n",ARGC > "/dev/stderr"
  115.     for (i in ARGV)
  116.         printf "arg %d: %s\n",i,ARGV[i] > "/dev/stderr"
  117.     }
  118.  
  119. ## Set up submit options array
  120.     if ("i" in Options) {
  121.     SubmitOpts["l"]
  122.     SubmitOpts["n"]
  123.     }
  124.     if ("w" in Options) {
  125.     SubmitOpts["w"]
  126.     SubmitOpts["W"]
  127.     }
  128.     if ("q" in Options)
  129.     SubmitOpts["q"]
  130.     if ("t" in Options)
  131.     SubmitOpts["t"]
  132.     if ("k" in Options)
  133.     SubmitOpts["k"] = Options["k"]
  134.     if ("L" in Options)
  135.     logFile = Options["L"]
  136.     if (Opt2Set(Options,"o",oSet))
  137.     for (val in oSet) {
  138.         c = substr(val,1,1)
  139.         if ((r = SubmitParam(c)) == "") {
  140.         printf "%s: Bad submit option (exiting): %s\n",
  141.         Name,c > "/dev/stderr"
  142.         exit 1
  143.         }
  144.         else if (r "" == c) {
  145.         len = length(val)
  146.         for (i = 1; i <= len; i++) {
  147.             c = substr(val,i,1)
  148.             if ((r = SubmitParam(c)) != c) {
  149.             if (r == "") {
  150.                 printf "%s: Bad submit option (exiting): %s\n",
  151.                 Name,c > "/dev/stderr"
  152.                 exit 1
  153.             }
  154.             else {
  155.                 printf \
  156.         "%s: submit option '%s' takes a value; must be given separately.\n",
  157.                 Name,c > "/dev/stderr"
  158.                 exit 1
  159.             }
  160.             }
  161.             SubmitOpts[c]
  162.             if (Debug)
  163.             printf "Setting submit option %s\n",c > "/dev/stderr"
  164.         }
  165.         }
  166.         else {
  167.         SubmitOpts[c] = substr(val,2)
  168.         if (Debug)
  169.             printf "Setting submit option %s to %s\n",
  170.             c,SubmitOpts[c] > "/dev/stderr"
  171.         }
  172.     }
  173.     if ("V" in Options) {
  174.     levList = "FAT,TMP,GEN,BST,FST,PTR,BTR,FTR"
  175.     split(levList,levSet,",")
  176.     if (logLevel in levSet)
  177.         SubmitOpts["V"] = levSet[logLevel]
  178.     else {
  179.         MakeSet(levNames,levList,",")
  180.         logLevel = toupper(Options["V"])
  181.         if (!(logLevel in levNames)) {
  182.         printf "%s: Bad logging level: %s.  Exiting.\n",
  183.         Name,logLevel > "/dev/stderr"
  184.         exit 1
  185.         }
  186.         SubmitOpts["V"] = logLevel
  187.     }
  188.     printf "%s: Logging set to level %s\n",
  189.     Name,SubmitOpts["V"] > "/dev/stderr"
  190.     }
  191.     UseSubmit = "b" in Options
  192.     UseUux = "u" in Options
  193.     UseRmail = "r" in Options
  194.     if (!(UseSubmit || UseUux || UseRmail)) {
  195.     print "Must give one of -b, -u, or -r." > "/dev/stderr"
  196.     exit 1
  197.     }
  198.     if (!UseSubmit && !IsEmpty(SubmitOpts)) {
  199.     print "Must give -b option if any submit-specific options are used." \
  200.     > "/dev/stderr"
  201.     exit 1
  202.     }
  203.  
  204. ## General stuff
  205.     Fields["X-Mailer"] = "AwkMail 2.1"
  206.     Quiet = "Q" in Options
  207.     TestMail = ("T" in Options)
  208.     if ("S" in Options) {
  209.     returnAddress = Options["S"]
  210.     if (UseSubmit)
  211.         Fields["From"] = returnAddress
  212.     }
  213.     if ("M" in Options)
  214.     Fields["From"] = Options["M"]
  215.     
  216.     if (!("a" in Options))
  217.     GetAliases(Aliases,Debug > 5)
  218.     split("",SeenAddrs)    # for awk
  219.     numAddrs = ExpandAliases(ARGV,Addrs,0,SeenAddrs,Debug > 2)
  220.  
  221.     # Ask for a subject if none given on cmd line;
  222.     # if subject was given on cmd line, print it
  223.     if ("s" in Options)
  224.     subject = Options["s"]
  225.     else if (TestMail)
  226.     subject = "Test Mail"
  227.     if (!TestMail) {
  228.     if (!Quiet)
  229.         printf "Subject: "
  230.     if (subject == "") {
  231.         if (!MesgIn())
  232.         exit 0
  233.         subject = MesgLine
  234.     }
  235.     else if (!Quiet)
  236.         print subject 
  237.     if (!Quiet)
  238.         print ""
  239.     }
  240.     if (TestMail)
  241.     message = TestMailMessage
  242.     if (UseSubmit)
  243.     if ((Err = SubmitMessage(SubmitOpts,subject,returnAddress,Addrs,
  244.     Fields,message)) != "") {
  245.         print Err > "/dev/stderr"
  246.         exit 1
  247.     }
  248.     exit 0
  249. }
  250.  
  251. function SubmitMessage(SubmitOpts,subject,returnAddress,Addrs,Fields,Message,
  252. Cc,Bcc,Order,Cmd) {
  253.     # Make awk believe these are arrays
  254.     split("",Cc)
  255.     split("",Bcc)
  256.     split("",Order)
  257.     Cmd = InitMail(Addrs,Cc,Bcc,Fields,Order,subject,returnAddress,1,
  258.     SubmitOpts,Debug > 3)
  259.     if (Cmd ~ "^!")
  260.     return substr(Cmd,2)
  261.     if (Message != "")
  262.     print Message | Cmd
  263.     else
  264.     while (MesgIn())
  265.         print MesgLine | Cmd
  266.     if (Debug)
  267.     print "Closing " Cmd "..." > "/dev/stderr"
  268.     close(Cmd)
  269.     if (Debug)
  270.     print "Done closing " Cmd "." > "/dev/stderr"
  271.     return ""
  272. }
  273.  
  274.  
  275. function MesgIn () {
  276.     if ((getline MesgLine < "/dev/stdin") == 1 && (MesgLine != "."))
  277.     return 1
  278.     else {
  279.     MesgLine = ""
  280.     return 0
  281.     }
  282. }
  283.  
  284. # Read body of message.
  285. # If OneLine is true, only one line is read, just enough to confirm that
  286. # there will be a body to the message.
  287. # The returned buffer will not have a newline after the last line.
  288. function ReadBody(OneLine,  message,LineCount) {
  289.     # Read lines until EOF reached,
  290.     # or a line consisting solely of "." is entered
  291.     MesgLine = ""
  292.     if (MesgIn()) {
  293.     message = MesgLine
  294.     LineCount = 1
  295.     if (!OneLine)
  296.         while (MesgIn()) {
  297.         message = "\n" message MesgLine
  298.         LineCount++
  299.         }
  300.     if (Debug > 4)
  301.         printf "So far: %d lines in message body.\n",
  302.         LineCount > "/dev/stderr"
  303.     }
  304.     if (!LineCount)
  305.     print "Warning: No message body." > "/dev/stderr"
  306.     return message
  307. }
  308.  
  309. function GetAliases(Aliases,Verbose) {
  310.     if ("MAILRC" in ENVIRON)
  311.     ProcAliasFile(ENVIRON["MAILRC"],Aliases,Verbose)
  312.     else if ("MUSHRC" in ENVIRON)
  313.     ProcAliasFile(ENVIRON["MUSHRC"],Aliases,Verbose)
  314.     else if ("HOME" in ENVIRON)
  315.     ProcAliasFile(ENVIRON["HOME"] "/.mailrc",Aliases,Verbose)
  316. }
  317.  
  318. function ProcAliasFile(file,Aliases,Verbose,  extended,alias,lastalias) {
  319.     # mailx allows "alias" or "group"
  320.     alias = "(alias|group)"
  321.     if (Verbose)
  322.     print "Reading aliases from " file "..."
  323.     while ((getline < file) == 1) {
  324.         if (extended) {
  325.             if ($0 ~ /\\[ \t]*$/)
  326.                 sub(/[ \t]*\\[ \t]*$/," ")
  327.             else {
  328.         if (Verbose)
  329.             print lastalias ": " Aliases[lastalias] $0
  330.                 extended = 0
  331.         }
  332.             Aliases[lastalias] = Aliases[lastalias] $0
  333.         }
  334.         else if ($1 ~ "^" alias "$") {
  335.             lastalias = $2
  336.             sub("^[ \t]*" alias "[ \t]+" $2 "[ \t]+","")
  337.             if ($0 ~ /\\[ \t]*$/) {
  338.                 sub(/[ \t]*\\[ \t]*$/," ")
  339.                 extended = 1
  340.             }
  341.         else if (Verbose)
  342.         print lastalias ": " $0
  343.             Aliases[lastalias] = $0
  344.         }
  345.     }
  346.     if (Verbose)
  347.     print "Done reading aliases from " file "."
  348.     close(file)
  349. }
  350.  
  351. # ExpandAliases() is passed a set of addresses in Addrs[], which have integer
  352. # indexes starting with 1.  They are copied to NewAddrs[] with indexes starting
  353. # with NewAddrsInd.  Aliases are expanded recursively.  The final value of
  354. # NewAddrsInd is returned.  SeenAddrs[] is used to skip duplicate addresses and
  355. # self-referential aliases.
  356. function ExpandAliases(Addrs,NewAddrs,NewAddrsInd,SeenAddrs,Debug,
  357. Addr,AliasAddrs,AddrInd) {
  358.     for (AddrInd = 1; AddrInd in Addrs; AddrInd++) {
  359.     Addr = Addrs[AddrInd]
  360.     if (!(Addr in SeenAddrs)) {
  361.         SeenAddrs[Addr]
  362.         if (Addr in Aliases) {
  363.         if (Debug)
  364.             printf "Expanding alias \"%s\" to: %s\n",
  365.             Addr,Aliases[Addr] > "/dev/stderr"
  366.         split(Aliases[Addr],AliasAddrs,"[ \t]+")
  367.         NewAddrsInd = \
  368.         ExpandAliases(AliasAddrs,NewAddrs,NewAddrsInd,SeenAddrs,Debug)
  369.         }
  370.         else
  371.         NewAddrs[++NewAddrsInd] = Addr
  372.     }
  373.     }
  374.     return NewAddrsInd
  375. }
  376.  
  377. ### Start of UUCP functions
  378. # Echo header & message into uux with command to rmail recipients
  379. # If ReadIn is true, after echoing read input & write to proc until done.
  380. function SendMessage(System,ReadIn,UseRmail,UseSubmit,Header,message,users,
  381. proc,UserList,i,User) {
  382.     if (!UseSubmit && (System == "LOCAL" || System == machine || \
  383.     System == domname || System == locname))
  384.     proc = "/usr/bin/rmail " users[System]
  385.     else if (UseRmail || UseSubmit) {
  386.     split(users[System],UserList)
  387.     if (UseRmail)
  388.         proc = "/usr/bin/rmail"
  389.     else
  390.         proc = "/usr/mmdf/bin/submit -" "vk10*xto*"
  391.     for (i in UserList) {
  392.         if ((User = UserList[i]) != "")
  393.         proc = proc " " System "!" User
  394.     }
  395.     }
  396.     else
  397.         # parenthesize recipients so their !s will not be interpreted by local
  398.         # uux.  Use -r since we might want to look at the result of this
  399.     # program before it's xferred.
  400.         proc = "uux -r - " System "!rmail \\(" users[System] "\\)"
  401.     if (Debug)
  402.     printf "\nExecuting command: %s\nHeader is:\n%s",
  403.     proc,Header > "/dev/stderr"
  404.     print Header message | proc
  405.     if (ReadIn)
  406.     while (MesgIn())
  407.         print MesgLine | proc
  408.     if (Debug)
  409.     print "Closing " proc "..." > "/dev/stderr"
  410.     close(proc)
  411.     if (Debug)
  412.     print "Done closing " proc "." > "/dev/stderr"
  413. }
  414.  
  415. function uusendMail() {
  416.     SepMachine(ARGV,ARGC - 1,Aliases,users,Debug > 4)
  417.     # Create date line of the form  Wed, 13 Mar 91 01:46:09 PST
  418.     date = fmtdate("%a, %d %h %y %T %Z")
  419.     # Create date line of the form  Wed Mar 13 01:46:09 1991
  420.     if (something) {
  421.     fromdate = fmtdate("%a %h %d %T 19%y")
  422.     from = from_ = returnAddress
  423.     }
  424.     else {
  425.     from_ = ENVIRON["USER"]
  426.     from = from_ "@" domname
  427.     }
  428.  
  429.     h1 = "From " from_ " " fromdate " remote from " machine "\n"
  430.     NumSys = 0
  431.     for (SysName in users)
  432.     if (++NumSys == 2)
  433.         break
  434.     OneSys = NumSys < 2
  435.  
  436.     CannedMessage = message == ""
  437.     if (!CannedMessage)
  438.     message = ReadBody(OneSys)
  439.  
  440.     if ((!OneSys && !Quiet) || Debug)
  441.     printf "Mailing..." > "/dev/stderr"
  442.     for (System in users) {
  443.         SendMessage(System,OneSys && !CannedMessage,useRmail,UseSubmit,
  444.     Header,message,users)
  445.     if (Debug)
  446.         printf "\nSending mail for users on system %s...",
  447.         System > "/dev/stderr"
  448.     }
  449.     if (!Quiet)
  450.     print "Done." > "/dev/stderr"
  451.     exit 0
  452. }
  453.  
  454. # Separates addresses by the machine they are on
  455. # Sets users[site] to contain a space-separated list of users at that site
  456. function SepMachine(Addrs,NumAddrs,Aliases,users,verbose,
  457. elem,Addr,AddrInd,AliasAddrs) {
  458.     for (AddrInd = 1; NumAddrs; AddrInd++) {
  459.     Addr = Addrs[AddrInd]
  460.     NumAddrs--
  461.     # If address contains an "@", field 1 is username, field 2 is sitename
  462.         if (Addr ~ "@") {
  463.             split(Addr,elem,"@")
  464.             users[elem[2]] = users[elem[2]] elem[1] " "
  465.         } else if (Addr ~ "!") {
  466.         # If address contains "!"s, first field is site to send mail to,
  467.         # rest of address is address to send to at site
  468.             split(Addr,elem,"!")
  469.             # remove first site and "!" that follows it from path
  470.             sub(elem[1] "!","",Addr)
  471.             users[elem[1]] = users[elem[1]] " " Addr
  472.         } else if (Addr != "-")  # Other addresses are local or aliases
  473.         users["LOCAL"] = users["LOCAL"] " " Addr
  474.     }
  475. }
  476. ### end of UUCP functions
  477.  
  478. ### Begin set library
  479. # 96/05/23 added return values  jhdiii
  480. # 96/05/25 added set2list()
  481. # 97/01/26 Added AOnly(), Exclusive()
  482.  
  483. # Return value: the number of new elements added to Inter
  484. function Intersection(A,B,Inter,  Elem,Count) {
  485.     for (Elem in A)
  486.     if (Elem in B && !(Elem in Inter)) {
  487.         Inter[Elem]
  488.         Count++
  489.     }
  490.     return Count
  491. }
  492.  
  493. # Any element that is in A or B but not both and which is not already in
  494. # Excl is added to Excl.
  495. # Return value: the number of new elements added to Excl
  496. function Exclusive(A,B,Excl) {
  497.     return AOnly(A,B,Excl) + AOnly(B,A,Excl)
  498. }
  499.  
  500. # Any element that is in A and not in B or aOnly is added to aOnly.
  501. # Return value: the number of new elements added to aOnly.
  502. function AOnly(A,B,aOnly,  Elem,Count) {
  503.     for (Elem in A)
  504.     if (!(Elem in B) && !(Elem in aOnly)) {
  505.         aOnly[Elem]
  506.         Count++
  507.     }
  508.     return Count
  509. }
  510.  
  511. # Return value: the number of new elements added to Both
  512. function Union(A,B,Both) {
  513.     return CopySet(A,Both) + CopySet(B,Both)
  514. }
  515.  
  516. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  517. # Return value: the number of elements deleted.
  518. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  519.     for (Elem in Subtrahend)
  520.     if (Elem in Minuend) {
  521.         delete Minuend[Elem]
  522.         nDel++
  523.     }
  524.     return nDel
  525. }
  526.  
  527. # Return value: the number of new elements added to To
  528. function CopySet(From,To,  Elem,n) {
  529.     for (Elem in From)
  530.     if (!(Elem in To)) {
  531.         To[Elem]
  532.         n++
  533.     }
  534.     return n
  535. }
  536.  
  537. # Returns 1 if Set is empty, 0 if not.
  538. function IsEmpty(Set,  i) {
  539.     for (i in Set)
  540.     return 0
  541.     return 1
  542. }
  543.  
  544. # MakeSet: make a set from a list.
  545. # An index with the name of each element of the list is created in the given
  546. # array.
  547. # Input variables:
  548. # Elements is a string containing the list of elements.
  549. # Sep is the character that separates the elements of the list.
  550. # Output variables:
  551. # Set is the array.
  552. # Return value: the number of new elements added to the set.
  553. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  554.     nFound = 0
  555.     Num = split(Elements,Names,Sep)
  556.     for (i = 1; i <= Num; i++) {
  557.     ind = Names[i]
  558.     if (!(ind in Set)) {
  559.         Set[ind]
  560.         nFound++
  561.     }
  562.     }
  563.     return nFound
  564. }
  565.  
  566. # Returns the number of elements in set Set
  567. function NumElem(Set,  elem,Num) {
  568.     for (elem in Set)
  569.     Num++
  570.     return Num
  571. }
  572.  
  573. # Remove all elements from Set
  574. function DeleteAll(Set,  i) {
  575.     split("",Set,",")
  576. }
  577.  
  578. # Returns a list of all of the elements in Set[], with each pair of elements
  579. # separated by Sep.
  580. function set2list(Set,Sep,  list,elem) {
  581.     for (elem in Set)
  582.     list = list Sep elem
  583.     return substr(list,2)    # skip 1st separator
  584. }
  585. ### End set library
  586. ### Start of mail sending routines.
  587. # @(#) mail-send.gawk 2.0 97/02/22
  588. # 96/01/29 john h. dubois iii (john@armory.com)
  589. # 97/02/15 Rewritten.
  590. #
  591. # Returns name of cmd to pipe into, ready for body of message.
  592. # To[], Cc[], Fields[], and Order[] are as described for header822()
  593. # Bcc[] is an additional list of recipients who should not be mentioned in
  594. # headers.
  595. # If a non-null Subject is passed, as a convenience it is added to Fields[]
  596. # before it is passed to header822().
  597. # If a non-null From is passed, it is used as the return address.  It does
  598. # not affect the headers unless the MTA records it in them.
  599. function InitMail(To,Cc,Bcc,Fields,Order,Subject,From,UseSubmit,SubmitOpts,
  600. Debug,  Recips,i,j,Cmd) {
  601.     for (i = 1; i in To; i++)
  602.     Recips[++j] = To[i]
  603.     for (i = 1; i in Cc; i++)
  604.     Recips[++j] = Cc[i]
  605.     for (i = 1; i in Bcc; i++)
  606.     Recips[++j] = Bcc[i]
  607.     if (UseSubmit)
  608.     Cmd = InitSubmit(Recips,From,SubmitOpts,Debug)
  609.     else
  610.     Cmd = InitSendmail(Recips,From)
  611.     if (Cmd ~ "^!")
  612.     return Cmd
  613.     if (Subject != "")
  614.     Fields["Subject"] = Subject
  615.     printf "%s\n",header822(Fields,To,Cc,Order) | Cmd
  616.     return Cmd
  617. }
  618.  
  619. # Sets up globals _ParamSOpts[] and _NoParamSOpts[] for use as sets of 
  620. # submit options to do and do not take values.
  621. function SubmitParam(Opt,Val,  i,c) {
  622.     if (!("r" in _NoParamSOpts)) {
  623.     for (i = 1; (c = substr("Wcdhjlmnqrstuvwz",i,1)) != ""; i++)
  624.         _NoParamSOpts[c]
  625.     for (i = 1; (c = substr("LUVfghikx",i,1)) != ""; i++)
  626.         _ParamSOpts[c]
  627.     }
  628.     if (Opt in _ParamSOpts)
  629.     return Opt Val "*"
  630.     else if (Opt in _NoParamSOpts)
  631.     return Opt
  632.     else
  633.     return ""
  634. }
  635.  
  636. # Return value: Command to pipe into.  If an invalid submit option is passed,
  637. # a null string is returned.
  638. # Submit options are:
  639. # i*    Source channel
  640. # h*    Source host
  641. # t    Trust Sender/From line (root/mmdf only)
  642. # u    Don't trust Sender/From line (add Source-Info line to header)
  643. # f*    Don't trust Sender/From line; add given text
  644. # x*    Extract recipient list from named fields (comma-separated list)
  645. #       RecipList[] will not be used if this option is given.
  646. # g*    Extract recipient list from named fields and use explicit list
  647. # v    Report validity of each address given, rather than aborting on any bad
  648. # m    Deliver to mailbox (default; there used to be a tty option as well).
  649. #       m is always turned on for the sake of old versions.
  650. # l    Deliver local mail immediately; overrides mod=reg in MMDF config
  651. # n    Deliver netmail immediately; overrides mod=reg in MMDF config
  652. # w    Watch immediate delivery attempts
  653. # r    Return undelivered mail to submitter when it expires
  654. # s    Return undelivered mail to address given by 'Sender:' when it expires
  655. # q    Do not return undelivered mail (discard it when it expires).
  656. #       Remote systems may still return mail.  Use q with null From parameter
  657. #     for no returns at all.
  658. # c    If mail is returned, include only a citation of the contents
  659. # z    Do not send warnings re undelivered (but not expired) mail
  660. # d    Don't use delay channel.  If 1st nameserver use fails, mail is returned
  661. # j    Used by the delay channel to indicate that submission is by it
  662. # k*    Specify nameserver timeout.  Parameter is in seconds.
  663. # W    Watch submission.  Output is sent to fd 2
  664. # L*    Specify logfile (root/mmdf only)
  665. # V*    Specify logging level (root/mmdf only). FAT TMP GEN BST FST PTR BTR FTR
  666. # U*    Specify invoker's UID (root only)
  667. function InitSubmit(RecipList,From,SubmitOpts,Debug,  Cmd,i,c,Opt) {
  668.     # If not special return handling requested and no return address given,
  669.     # send returns to submitter
  670.     if (From == "" && !("s" in SubmitOpts || "q" in SubmitOpts))
  671.     SubmitOpts["r"]
  672.     SubmitOpts["m"]
  673.     for (Opt in SubmitOpts)
  674.     if ((c = SubmitParam(Opt,SubmitOpts[Opt])) == "")
  675.         return "!Error initializing submit: Bad option '" Opt "'."
  676.     else
  677.         Cmd = Cmd c
  678.     if (Cmd != "")
  679.     Cmd = " -" Cmd
  680.     Cmd = "exec /usr/mmdf/bin/submit" Cmd
  681.     if (Debug) {
  682.     Cmd = "exec tee /dev/tty | " Cmd
  683.     print "mail submission command: " Cmd > "/dev/stderr"
  684.     }
  685.     if (!("r" in SubmitOpts || "s" in SubmitOpts))
  686.     print From | Cmd    # Explicit return address - maybe empty
  687.     if (!("x" in SubmitOpts)) {    # If explicit addresses may be given
  688.     for (i = 1; i in RecipList; i++)
  689.         print RecipList[i] | Cmd
  690.     print "!" | Cmd    # terminate recipient list
  691.     }
  692.     return Cmd
  693. }
  694.  
  695. function InitSendmail(RecipList,From,  ToList,i) {
  696.     if (From != "")
  697.     From = " -f '" From "'"
  698.     for (i = 1; i in RecipList; i++)
  699.     ToList = ToList " " RecipList[i]
  700.     return "exec /usr/lib/sendmail" From ToList
  701. }
  702.  
  703. # @(#) GetMailHostName 97/02/12
  704. # 91/03/13 jhdiii
  705. # 97/02/12 Use hostname if unable to get name from mmdftailor.
  706. # Returns the name of the local host that should be used for mail purposes.
  707. # If mmdftailor is readable and both MLNAME and MLDOMAIN can be found, uses
  708. # MLNAME.MLDOMAIN.  If not, uses 'hostname'.
  709. # The name is stored in the global _MailHostName for reuse by this function.
  710. function GetMailHostName(  mlname,mldomain,proc,tailor,oFS,hostname) {
  711.     if (_MailHostName != "")
  712.     return _MailHostName
  713.     tailor = "/usr/mmdf/mmdftailor"
  714.     oFS = FS
  715.     FS = " "    # normal awk field splitting
  716.     while ((getline < tailor) == 1) {
  717.         if ($1 == "MLNAME")
  718.             mlname = $2
  719.         else if ($1 == "MLDOMAIN")
  720.             mldomain = $2
  721.     else
  722.         continue
  723.     if (mlname != "" && mldomain != "") {
  724.         hostname = mlname "." mldomain
  725.         gsub("\"","",hostname)    # in case values are quoted
  726.         break
  727.     }
  728.     }
  729.     close(tailor)
  730.     if (hostname == "") {
  731.     proc = "/usr/bin/hostname"
  732.     proc | getline hostname
  733.     close(proc)
  734.     }
  735.     FS = oFS
  736.     _MailHostName = hostname
  737.     return hostname
  738. }
  739.  
  740. # Returns an RFC822 recipient field, wrapped as neccessary, ending with a
  741. # newline.
  742. function WrapField822(FieldName,Values,
  743. Field,Line,len,w,i,val,indentLen,indentStr)
  744. {
  745.     Line = FieldName ":"
  746.     len = length(Line)
  747.     indentLen = len+1
  748.     for (i = 1; i <= indentLen; i++)
  749.     indentStr = indentStr " "
  750.     for (i = 1; i in Values; i++) {
  751.     val = Values[i]
  752.     if ((i+1) in Values)
  753.         val = val ","
  754.     len += w = length(val)+1
  755.     if (len > 79) {
  756.         Field = Field Line "\n"
  757.         Line = indentStr val
  758.         len = w + indentLen - 1
  759.     }
  760.     else
  761.         Line = Line " " val
  762.     }
  763.     return Field Line "\n"
  764. }
  765.  
  766. # Create an RFC822-compliant mail header.  A blank line is *not* appended;
  767. # the returned value ends with a single trailing newline.
  768. # Fields[] contains field values, indexed by name.  The name is given without
  769. # a trailing :.  To[] and Cc[] are the recipient lists.
  770. # The first line of the header is the Date: field.  If there is no Date index
  771. # in Fields[], the current date & time in an RFC822-compliant format is used.
  772. # The second line of the header is the From: field.  If there is no From index
  773. # in Fields[], the From: field is made "user@host (name)", where user is the
  774. # value of the USER environment variable, or the name from 'id' if it is not
  775. # set; host is as described for GetMailHostName(), and name is from the NAME
  776. # environment variable.  If NAME is not set, (name) is not included in the
  777. # From: field.
  778. # The next lines of the header give the To: and (optionally) Cc: fields.
  779. # To[] and Cc[] should contain values indexed by integers starting with 1.
  780. # The fields are built by concatenating these values in the order of their
  781. # indices.  Header extensions are used as neccessary to keep the length of
  782. # each physical line below 80 characters if possible.
  783. # After these are added, any other fields are added.
  784. # The order they are added in may be specified by assigning the header names to
  785. # consecutive integer indexes in Order[], starting with 1.
  786. # Any field named in Order[] that does not exist in Fields[] will not be added.
  787. # Date, From, To, and Cc should not be given in Order.
  788. # After all fields named in Order[] are added, any remaining fields are added.
  789. # Field wrapping is not done to any values in Fields.
  790. # All elements are removed from Fields[].
  791. # Minimal RFC822 message has From, Date, and either To or Bcc line.
  792. function header822(Fields,To,Cc,Order,  header,i,field) {
  793.     if ("Date" in Fields) {
  794.     header = Fields["Date"]
  795.     delete Fields["Date"]
  796.     }
  797.     else
  798.     header = fmtdate("%a, %d %h %Y %T %Z")
  799.     header = "Date: " header "\nFrom: "
  800.     if ("From" in Fields) {
  801.     header = header Fields["From"]
  802.     delete Fields["From"]
  803.     }
  804.     else {
  805.     header = header WhoAmI()
  806.     header = header "@" GetMailHostName()
  807.     if ("NAME" in ENVIRON)
  808.         header = header " (" ENVIRON["NAME"] ")"
  809.     }
  810.     header = header "\n" WrapField822("To",To)
  811.     if (1 in Cc)
  812.     header = header WrapField822("Cc",Cc)
  813.     for (i = 1; i in Order; i++)
  814.     if ((field = Order[i]) in Fields) {
  815.         header = header field ": " Fields[field] "\n"
  816.         delete Fields[field]
  817.     }
  818.     for (field in Fields) {
  819.     header = header field ": " Fields[field] "\n"
  820.     delete Fields[field]
  821.     }
  822.     return header
  823. }
  824. ### End of mail sending routines.
  825.  
  826. # WhoAmI 1.0 97/02/14
  827. # 97/02/14 john h. dubois iii (john@armory.com)
  828. # WhoAmI: return best attempt at determining what user owns this process.
  829. # First, get USER from environment.  If that fails, try logname; it gives a
  830. # better indication of who the user is than the uid does, since multiple login
  831. # names may have the same uid.  But, check that the name returned by logname
  832. # maps to the process' uid, as utmp may have bogus data or the user may have
  833. # su'd.  If it doesn't, or logname fails, use the user name returned by id.
  834. # For efficiency in multiple invokations, the user name is stored in
  835. # _WhoAmI_user for reuse.
  836. function WhoAmI(  Cmd,line,elem,logname,uiduser,uid,oFS) {
  837.     if (_WhoAmI_user != "")
  838.     return _WhoAmI_user 
  839.     if ("USER" in ENVIRON && ENVIRON["USER"] != "")
  840.     return _WhoAmI_user = ENVIRON["USER"]
  841.     Cmd = "exec /usr/bin/logname 2>/dev/null"
  842.     Cmd | getline logname
  843.     close(Cmd)
  844.     Cmd = "exec /usr/bin/id"
  845.     Cmd | getline line
  846.     close(Cmd)
  847.     split(line,elem,"[()=]")
  848.     uiduser = elem[3]
  849.     if (logname == uiduser)
  850.     return _WhoAmI_user = logname
  851.     uid = elem[2]
  852.     oFS = FS
  853.     FS = ":"
  854.     while ((getline < "/etc/passwd") == 1)
  855.     if ($1 == logname) {
  856.         if ($3 == uid)
  857.         return _WhoAmI_user = logname
  858.         break
  859.     }
  860.     return _WhoAmI_user = uiduser
  861. }
  862. ### Start of ProcArgs library
  863. # @(#) ProcArgs 1.12 97/02/22
  864. # 92/02/29 john h. dubois iii (john@armory.com)
  865. # 93/07/18 Added "#" arg type
  866. # 93/09/26 Do not count -h against MinArgs
  867. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  868. #          Removed meaning of "+" or "-" by itself.
  869. # 94/03/08 Added & option and *()< option types.
  870. # 94/04/02 Added NoRCopt to Opts()
  871. # 94/06/11 Mark numeric variables as such.
  872. # 94/07/08 Opts(): Do not require any args if h option is given.
  873. # 95/01/22 Record options given more than once.  Record option num in argv.
  874. # 95/06/08 Added ExclusiveOptions().
  875. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  876. #          Expand $VARNAME at the start of its filenames.
  877. #          Let varname=0 and -option- turn off an option.
  878. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  879. #          of the vars should be searched for in the environment.
  880. #          Check for duplicate rcfiles.
  881. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  882. #          now return various negatives values on error, not just -1, and
  883. #          Opts() may set Err to various positive values, not just 1.
  884. #          Added AllowUnrecOpt.
  885. # 96/05/23 Check type given for & option
  886. # 96/06/15 Re-port to awk
  887. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  888. #          used by other functions.
  889. # 96/10/15 Added OptChars
  890. # 96/11/01 Added exOpts arg to Opts()
  891. # 96/11/16 Added ; type
  892. # 96/12/08 Added Opt2Set() & Opt2Sets()
  893. # 96/12/27 Added CmdLineOpt()
  894. # 97/02/22 Remove packed elements.
  895.  
  896. # optlist is a string which contains all of the possible command line options.
  897. # A character followed by certain characters indicates that the option takes
  898. # an argument, with type as follows:
  899. # :    String argument
  900. # ;    Non-empty string argument
  901. # *    Floating point argument
  902. # (    Non-negative floating point argument
  903. # )    Positive floating point argument
  904. # #    Integer argument
  905. # <    Non-negative integer argument
  906. # >    Positive integer argument
  907. # The only difference the type of argument makes is in the runtime argument
  908. # error checking that is done.
  909.  
  910. # The & option is a special case used to get numeric options without the
  911. # user having to give an option character.  It is shorthand for [-+.0-9].
  912. # If & is included in optlist and an option string that begins with one of
  913. # these characters is seen, the value given to "&" will include the first
  914. # char of the option.  & must be followed by a type character other than ":"
  915. # or ";".
  916. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  917.  
  918. # Strings in argv[] which begin with "-" or "+" are taken to be
  919. # strings of options, except that a string which consists solely of "-"
  920. # or "+" is taken to be a non-option string; like other non-option strings,
  921. # it stops the scanning of argv and is left in argv[].
  922. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  923. # If an option takes an argument, the argument may either immediately
  924. # follow it or be given separately.
  925. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  926. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  927. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  928. # this feature had a flaw that caused problems in some cases.  See the OptChars
  929. # parameter to explicitly set the option-specifier characters.
  930.  
  931. # If an option that does not take an argument is given,
  932. # an index with its name is created in Options and its value is set to the
  933. # number of times it occurs in argv[].
  934.  
  935. # If an option that does take an argument is given, an index with its name is
  936. # created in Options and its value is set to the value of the argument given
  937. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  938. # If an option that takes an argument is given more than once,
  939. # Options[option-name,"count"] is incremented, and the value is assigned to
  940. # the index (option-name,instance) where instance is 2 for the second occurance
  941. # of the option, etc.
  942. # In other words, the first time an option with a value is encountered, the
  943. # value is assigned to an index consisting only of its name; for any further
  944. # occurances of the option, the value index has an extra (count) dimension.
  945.  
  946. # The sequence number for each option found in argv[] is stored in
  947. # Options[option-name,"num",instance], where instance is 1 for the first
  948. # occurance of the option, etc.  The sequence number starts at 1 and is
  949. # incremented for each option, both those that have a value and those that
  950. # do not.  Options set from a config file have a value of 0 assigned to this.
  951.  
  952. # Options and their arguments are deleted from argv.
  953. # Note that this means that there may be gaps left in the indices of argv[].
  954. # If compress is nonzero, argv[] is packed by moving its elements so that
  955. # they have contiguous integer indices starting with 0.
  956. # Option processing will stop with the first unrecognized option, just as
  957. # though -- was given except that unlike -- the unrecognized option will not be
  958. # removed from ARGV[].  Normally, an error value is returned in this case.
  959. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  960. # be found, so the number of remaining arguments is returned instead.
  961. # If OptChars is not a null string, it is the set of characters that indicate
  962. # that an argument is an option string if the string begins with one of the
  963. # characters.  A string consisting solely of two of the same option-indicator
  964. # characters stops the scanning of argv[].  The default is "-+".
  965. # argv[0] is not examined.
  966. # The number of arguments left in argc is returned.
  967. # If an error occurs, the global string OptErr is set to an error message
  968. # and a negative value is returned.
  969. # Current error values:
  970. # -1: option that required an argument did not get it.
  971. # -2: argument of incorrect type supplied for an option.
  972. # -3: unrecognized (invalid) option.
  973. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  974. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  975. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  976. {
  977. # ArgNum is the index of the argument being processed.
  978. # ArgsLeft is the number of arguments left in argv.
  979. # Arg is the argument being processed.
  980. # ArgLen is the length of the argument being processed.
  981. # ArgInd is the position of the character in Arg being processed.
  982. # Option is the character in Arg being processed.
  983. # Pos is the position in OptList of the option being processed.
  984. # NumOpt is true if a numeric option may be given.
  985.     ArgsLeft = argc
  986.     NumOpt = index(OptList,"&")
  987.     OptionNum = 0
  988.     if (OptChars == "")
  989.     OptChars = "-+"
  990.     while (OptChars != "") {
  991.     c = substr(OptChars,1,1)
  992.     OptChars = substr(OptChars,2)
  993.     OptCharSet[c]
  994.     OptTerm[c c]
  995.     }
  996.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  997.     Arg = argv[ArgNum]
  998.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  999.         break    # Not an option; quit
  1000.     if (Arg in OptTerm) {
  1001.         delete argv[ArgNum]
  1002.         ArgsLeft--
  1003.         break
  1004.     }
  1005.     ArgLen = length(Arg)
  1006.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  1007.         Option = substr(Arg,ArgInd,1)
  1008.         if (NumOpt && Option ~ /[-+.0-9]/) {
  1009.         # If this option is a numeric option, make its flag be & and
  1010.         # its option string flag position be the position of & in
  1011.         # the option string.
  1012.         Option = "&"
  1013.         Pos = NumOpt
  1014.         # Prefix Arg with a char so that ArgInd will point to the
  1015.         # first char of the numeric option.
  1016.         Arg = "&" Arg
  1017.         ArgLen++
  1018.         }
  1019.         # Find position of flag in option string, to get its type (if any).
  1020.         # Disallow & as literal flag.
  1021.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  1022.         if (AllowUnrecOpt) {
  1023.             Escape = 1
  1024.             break
  1025.         }
  1026.         else {
  1027.             OptErr = "Invalid option: " specGiven Option
  1028.             return -3
  1029.         }
  1030.         }
  1031.  
  1032.         # Find what the value of the option will be if it takes one.
  1033.         # NeedNextOpt is true if the option specifier is the last char of
  1034.         # this arg, which means that if the option requires a value it is
  1035.         # the next arg.
  1036.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  1037.         if (GotValue = ArgNum + 1 < argc)
  1038.             Value = argv[ArgNum+1]
  1039.         }
  1040.         else {    # Value is included with option
  1041.         Value = substr(Arg,ArgInd + 1)
  1042.         GotValue = 1
  1043.         }
  1044.  
  1045.         if (HadValue = AssignVal(Option,Value,Options,
  1046.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  1047.         specGiven)) {
  1048.         if (HadValue < 0)    # error occured
  1049.             return HadValue
  1050.         if (HadValue == 2)
  1051.             ArgInd++    # Account for the single-char value we used.
  1052.         else {
  1053.             if (NeedNextOpt) {    # option took next arg as value
  1054.             delete argv[++ArgNum]
  1055.             ArgsLeft--
  1056.             }
  1057.             break    # This option has been used up
  1058.         }
  1059.         }
  1060.     }
  1061.     if (Escape)
  1062.         break
  1063.     # Do not delete arg until after processing of it, so that if it is not
  1064.     # recognized it can be left in ARGV[].
  1065.     delete argv[ArgNum]
  1066.     ArgsLeft--
  1067.     }
  1068.     if (compress != 0) {
  1069.     dest = 1
  1070.     src = argc - ArgsLeft + 1
  1071.     if (src != dest) {
  1072.         for (count = ArgsLeft - 1; count; count--) {
  1073.         ARGV[dest] = ARGV[src]
  1074.         dest++
  1075.         src++
  1076.         }
  1077.         for (; dest < src; dest++)
  1078.         delete ARGV[dest]
  1079.     }
  1080.     }
  1081.     return ArgsLeft
  1082. }
  1083.  
  1084. # Assignment to values in Options[] occurs only in this function.
  1085. # Option: Option specifier character.
  1086. # Value: Value to be assigned to option, if it takes a value.
  1087. # Options[]: Options array to return values in.
  1088. # ArgType: Argument type specifier character.
  1089. # GotValue: Whether any value is available to be assigned to this option.
  1090. # Name: Name of option being processed.
  1091. # OptionNum: Number of this option (starting with 1) if set in argv[],
  1092. #     or 0 if it was given in a config file or in the environment.
  1093. # SingleOpt: true if the value (if any) that is available for this option was
  1094. #     given as part of the same command line arg as the option.  Used only for
  1095. #     options from the command line.
  1096. # specGiven is the option specifier character use, if any (e.g. - or +),
  1097. # for use in error messages.
  1098. # Global variables: OptErr
  1099. # Return value: negative value on error, 0 if option did not require an
  1100. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  1101. # the arg.
  1102. # Current error values:
  1103. # -1: Option that required an argument did not get it.
  1104. # -2: Value of incorrect type supplied for option.
  1105. # -3: Bad type given for option &
  1106. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  1107. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  1108.     # If option takes a value...    [
  1109.     NumTypes = "*()#<>]"
  1110.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  1111.     OptErr = "Bad type given for & option"
  1112.     return -3
  1113.     }
  1114.  
  1115.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  1116.     if (!GotValue) {
  1117.         if (Name != "")
  1118.         OptErr = "Variable requires a value -- " Name
  1119.         else
  1120.         OptErr = "option requires an argument -- " Option
  1121.         return -1
  1122.     }
  1123.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1124.         OptErr = Err
  1125.         return -2
  1126.     }
  1127.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1128.     if (ArgType != ":" && ArgType != ";")
  1129.         Value += 0
  1130.     if ((Instance = ++Options[Option,"count"]) > 1)
  1131.         Options[Option,Instance] = Value
  1132.     else
  1133.         Options[Option] = Value
  1134.     }
  1135.     # If this is an environ or rcfile assignment & it was given a value...
  1136.     else if (!OptionNum && Value != "") {
  1137.     UsedValue = 1
  1138.     # If the value is "0" or "-" and this is the first instance of it,
  1139.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1140.     # turn off an option (for the simple "Option in Options" test) in such
  1141.     # a way that it cannot be turned on in a later file.
  1142.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1143.         Instance = 1
  1144.     else
  1145.         Instance = ++Options[Option]
  1146.     # Save the value even though this is a flag
  1147.     Options[Option,Instance] = Value
  1148.     }
  1149.     # If this is a command line flag and has a - following it in the same arg,
  1150.     # it is being turned off.
  1151.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1152.     UsedValue = 2
  1153.     if (Option in Options)
  1154.         Instance = ++Options[Option]
  1155.     else
  1156.         Instance = 1
  1157.     Options[Option,Instance]
  1158.     }
  1159.     # If this is a flag assignment without a value, increment the count for the
  1160.     # flag unless it was turned off.  The indicator for a flag being turned off
  1161.     # is that the flag index has not been set in Options[] but it has an
  1162.     # instance count.
  1163.     else if (Option in Options || !((Option,1) in Options))
  1164.     # Increment number of times this flag seen; will inc null value to 1
  1165.     Instance = ++Options[Option]
  1166.     Options[Option,"num",Instance] = OptionNum
  1167.     return UsedValue
  1168. }
  1169.  
  1170. # Option is the option letter
  1171. # Value is the value being assigned
  1172. # Name is the var name of the option, if any
  1173. # ArgType is one of:
  1174. # :    String argument
  1175. # ;    Non-null string argument
  1176. # *    Floating point argument
  1177. # (    Non-negative floating point argument
  1178. # )    Positive floating point argument
  1179. # #    Integer argument
  1180. # <    Non-negative integer argument
  1181. # >    Positive integer argument
  1182. # specGiven is the option specifier character use, if any (e.g. - or +),
  1183. # for use in error messages.
  1184. # Returns null on success, err string on error
  1185. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1186.     if (ArgType == ":")
  1187.     return ""
  1188.     if (ArgType == ";") {
  1189.     if (Value == "")
  1190.         Err = "must be a non-empty string"
  1191.     }
  1192.     # A number begins with optional + or -, and is followed by a string of
  1193.     # digits or a decimal with digits before it, after it, or both
  1194.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1195.     Err = "must be a number"
  1196.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1197.     Err = "may not include a fraction"
  1198.     else if (ArgType ~ "[()<>]" && Value < 0)
  1199.     Err = "may not be negative"
  1200.     # (
  1201.     else if (ArgType ~ "[)>]" && Value == 0)
  1202.     Err = "must be a positive number"
  1203.     if (Err != "") {
  1204.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1205.     if (Name != "")
  1206.         return ErrStr "variable " substr(Name,1,1) " " Err
  1207.     else {
  1208.         if (Option == "&")
  1209.         Option = Value
  1210.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1211.     }
  1212.     }
  1213.     else
  1214.     return ""
  1215. }
  1216.  
  1217. # Note: only the above functions are needed by ProcArgs.
  1218. # The rest of these functions call ProcArgs() and also do other
  1219. # option-processing stuff.
  1220.  
  1221. # Opts: Process command line arguments.
  1222. # Opts processes command line arguments using ProcArgs()
  1223. # and checks for errors.  If an error occurs, a message is printed
  1224. # and the program is exited.
  1225. #
  1226. # Input variables:
  1227. # Name is the name of the program, for error messages.
  1228. # Usage is a usage message, for error messages.
  1229. # OptList the option description string, as used by ProcArgs().
  1230. # MinArgs is the minimum number of non-option arguments that this
  1231. # program should have, non including ARGV[0] and +h.
  1232. # If the program does not require any non-option arguments,
  1233. # MinArgs should be omitted or given as 0.
  1234. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1235. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1236. # by the value of the environment variable HOME.  If a filename begins with
  1237. # $, the part from the character after the $ up until (but not including)
  1238. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1239. # environment; if found its value will be substituted, if not the filename will
  1240. # be discarded.
  1241. # rcfiles are read in the order given.
  1242. # Values given in them will not override values given on the command line,
  1243. # and values given in later files will not override those set in earlier
  1244. # files, because AssignVal() will store each with a different instance index.
  1245. # The first instance of each variable, either on the command line or in an
  1246. # rcfile, will be stored with no instance index, and this is the value
  1247. # normally used by programs that call this function.
  1248. # VarNames is a comma-separated list of variable names to map to options,
  1249. # in the same order as the options are given in OptList.
  1250. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1251. # searched for in the environment.  If set to -1, all values will be searched
  1252. # for in the environment.  Values given in the environment will override
  1253. # those given in the rcfiles but not those given on the command line.
  1254. # NoRCopt, if given, is an additional letter option that if given on the
  1255. # command line prevents the rcfiles from being read.
  1256. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1257. # ExclusiveOptions() for a description of exOpts.
  1258. # Special options:
  1259. # If x is made an option and is given, some debugging info is output.
  1260. # h is assumed to be the help option.
  1261.  
  1262. # Global variables:
  1263. # The command line arguments are taken from ARGV[].
  1264. # The arguments that are option specifiers and values are removed from
  1265. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1266. # The number of elements in ARGV[] should be in ARGC.
  1267. # After processing, ARGC is set to the number of elements left in ARGV[].
  1268. # The option values are put in Options[].
  1269. # On error, Err is set to a positive integer value so it can be checked for in
  1270. # an END block.
  1271. # Return value: The number of elements left in ARGV is returned.
  1272. # Must keep OptErr global since it may be set by InitOpts().
  1273. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1274. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1275.     if (MinArgs == "")
  1276.     MinArgs = 0
  1277.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1278.     optChars)
  1279.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1280.     if (ArgsLeft >= 0) {
  1281.         OptErr = "Not enough arguments"
  1282.         Err = 4
  1283.     }
  1284.     else
  1285.         Err = -ArgsLeft
  1286.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1287.     Name,OptErr,Usage > "/dev/stderr"
  1288.     exit 1
  1289.     }
  1290.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1291.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1292.     {
  1293.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1294.     Err = -e
  1295.     exit 1
  1296.     }
  1297.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1298.     {
  1299.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1300.     Err = 1
  1301.     exit 1
  1302.     }
  1303.     return ArgsLeft
  1304. }
  1305.  
  1306. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1307. # <variable-name><assignment-char><value>.
  1308. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1309. # line and whitespace between the variable name and the assignment character) 
  1310. # is stripped.  Lines that do not contain an assignment operator or which
  1311. # contain a null variable name are ignored, other than possibly being noted in
  1312. # the return value.  If more than one assignment is made to a variable, the
  1313. # first assignment is used.
  1314. # Input variables:
  1315. # File is the file to read.
  1316. # Comment is the line-comment character.  If it is found as the first non-
  1317. #     whitespace character on a line, the line is ignored.
  1318. # Assign is the assignment string.  The first instance of Assign on a line
  1319. #     separates the variable name from its value.
  1320. # If StripWhite is true, whitespace around the value (whitespace between the
  1321. #     assignment char and trailing whitespace on the line) is stripped.
  1322. # VarPat is a pattern that variable names must match.  
  1323. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1324. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1325. #     a line; no assignment operator is needed.  These variables are set in
  1326. #     the output array with a null value.  Lines containing nothing but
  1327. #     whitespace are still ignored.
  1328. # Output variables:
  1329. # Values[] contains the assignments, with the indexes being the variable names
  1330. #     and the values being the assigned values.
  1331. # Lines[] contains the line number that each variable occured on.  A flag set
  1332. #     is record by giving it an index in Lines[] but not in Values[].
  1333. # Return value:
  1334. # If any errors occur, a string consisting of descriptions of the errors
  1335. # separated by newlines is returned.  In no case will the string start with a
  1336. # numeric value.  If no errors occur,  the number of lines read is returned.
  1337. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1338. FlagsOK,
  1339. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1340.     if (Comment != "")
  1341.     Comment = "^" Comment
  1342.     AssignLen = length(Assign)
  1343.     if (VarPat == "")
  1344.     VarPat = "."    # null varname not allowed
  1345.     while ((Status = (getline Line < File)) == 1) {
  1346.     LineNum++
  1347.     sub("^[ \t]+","",Line)
  1348.     if (Line == "")        # blank line
  1349.         continue
  1350.     if (Comment != "" && Line ~ Comment)
  1351.         continue
  1352.     if (Pos = index(Line,Assign)) {
  1353.         Var = substr(Line,1,Pos-1)
  1354.         Val = substr(Line,Pos+AssignLen)
  1355.         if (StripWhite) {
  1356.         sub("^[ \t]+","",Val)
  1357.         sub("[ \t]+$","",Val)
  1358.         }
  1359.     }
  1360.     else {
  1361.         Var = Line    # If no value, var is entire line
  1362.         Val = ""
  1363.     }
  1364.     if (!FlagsOK && Val == "") {
  1365.         Errs = Errs \
  1366.         sprintf("\nBad assignment on line %d of file %s: %s",
  1367.         LineNum,File,Line)
  1368.         continue
  1369.     }
  1370.     sub("[ \t]+$","",Var)
  1371.     if (Var !~ VarPat) {
  1372.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1373.         LineNum,File,Var)
  1374.         continue
  1375.     }
  1376.     if (!(Var in Lines)) {
  1377.         Lines[Var] = LineNum
  1378.         if (Pos)
  1379.         Values[Var] = Val
  1380.     }
  1381.     }
  1382.     if (Status)
  1383.     Errs = Errs "\nCould not read file " File
  1384.     close(File)
  1385.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1386. }
  1387.  
  1388. # Variables:
  1389. # Data is stored in Options[].
  1390. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1391. # Global vars:
  1392. # Sets OptErr.  Uses ENVIRON[].
  1393. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1394. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1395. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1396. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1397.     split("",filesRead,"")    # make awk know this is an array
  1398.     NumVars = split(VarNames,Vars,",")
  1399.     TypesInd = Ret = 0
  1400.     if (EnvSearch == -1)
  1401.     EnvSearch = NumVars
  1402.     for (i = 1; i <= NumVars; i++) {
  1403.     Var = Vars[i]
  1404.     CharOpt = substr(OptList,++TypesInd,1)
  1405.     if (CharOpt ~ "^[:;*()#<>&]$")
  1406.         CharOpt = substr(OptList,++TypesInd,1)
  1407.     Map[Var] = CharOpt
  1408.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1409.     # Do not overwrite entries from environment
  1410.     if (i <= EnvSearch && Var in ENVIRON &&
  1411.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1412.         return Err
  1413.     }
  1414.  
  1415.     numrcFiles = split(rcFiles,fNames,":")
  1416.     for (i = 1; i <= numrcFiles; i++) {
  1417.     rcFile = fNames[i]
  1418.     if (rcFile ~ "^~/")
  1419.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1420.     else if (rcFile ~ /^\$/) {
  1421.         rcFile = substr(rcFile,2)
  1422.         match(rcFile,"^[a-zA-Z0-9_]*")
  1423.         envvar = substr(rcFile,1,RLENGTH)
  1424.         if (envvar in ENVIRON)
  1425.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1426.         else
  1427.         continue
  1428.     }
  1429.     if (rcFile in filesRead)
  1430.         continue
  1431.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1432.     # may be the same
  1433.     filesRead[rcFile]
  1434.     if ("x" in Options)
  1435.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1436.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1437.     if (retStr > 0)
  1438.         READ_RCFILE = 1
  1439.     else if (ret != "") {
  1440.         OptErr = retStr
  1441.         Ret = -1
  1442.     }
  1443.     for (Var in Lines)
  1444.         if (Var in Map) {
  1445.         if ((Err = AssignVal(Map[Var],
  1446.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1447.         Var in Values,Var,0)) < 0)
  1448.             return Err
  1449.         }
  1450.         else {
  1451.         OptErr = sprintf(\
  1452.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1453.         Lines[Var],rcFile)
  1454.         Ret = -1
  1455.         }
  1456.     }
  1457.  
  1458.     if ("x" in Options)
  1459.     for (Var in Map)
  1460.         if (Map[Var] in Options)
  1461.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1462.         "/dev/stderr"
  1463.         else
  1464.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1465.     return Ret
  1466. }
  1467.  
  1468. # OptSets is a semicolon-separated list of sets of option sets.
  1469. # Within a list of option sets, the option sets are separated by commas.  For
  1470. # each set of sets, if any option in one of the sets is in Options[] AND any
  1471. # option in one of the other sets is in Options[], an error string is returned.
  1472. # If no conflicts are found, nothing is returned.
  1473. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1474. # the exclusions presented by the first set of sets (ab,def,g) if:
  1475. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1476. # (a or b is in Options[]) AND (g is in Options) OR
  1477. # (d, e, or f is in Options[]) AND (g is in Options)
  1478. # An error will be returned due to the exclusions presented by the second set
  1479. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1480. # todo: make options given on command line unset options given in config file
  1481. # todo: that they conflict with.
  1482. function ExclusiveOptions(OptSets,Options,
  1483. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1484. SetNum,OSetNum) {
  1485.     NumSetSets = split(OptSets,SetSets,";")
  1486.     # For each set of sets...
  1487.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1488.     # NumSets is the number of sets in this set of sets.
  1489.     NumSets = split(SetSets[SetSet],Sets,",")
  1490.     # For each set in a set of sets except the last...
  1491.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1492.         s1 = Sets[SetNum]
  1493.         L1 = length(s1)
  1494.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1495.         # If any of the options in this set was given, check whether
  1496.         # any of the options in the other sets was given.  Only check
  1497.         # later sets since earlier sets will have already been checked
  1498.         # against this set.
  1499.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1500.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1501.             s2 = Sets[OSetNum]
  1502.             L2 = length(s2)
  1503.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1504.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1505.                 ErrStr = ErrStr "\n"\
  1506.                 sprintf("Cannot give both %s and %s options.",
  1507.                 c1,c2)
  1508.             }
  1509.     }
  1510.     }
  1511.     if (ErrStr != "")
  1512.     return substr(ErrStr,2)
  1513.     return ""
  1514. }
  1515.  
  1516. # The value of each instance of option Opt that occurs in Options[] is made an
  1517. # index of Set[].
  1518. # The return value is the number of instances of Opt in Options.
  1519. function Opt2Set(Options,Opt,Set,  count) {
  1520.     if (!(Opt in Options))
  1521.     return 0
  1522.     Set[Options[Opt]]
  1523.     count = Options[Opt,"count"]
  1524.     for (; count > 1; count--)
  1525.     Set[Options[Opt,count]]
  1526.     return count
  1527. }
  1528.  
  1529. # The value of each instance of option Opt that occurs in Options[] that
  1530. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1531. # Other values are made indexes of Set[].
  1532. # The return value is the number of instances of Opt in Options.
  1533. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1534.     ret = Opt2Set(Options,Opt,aSet)
  1535.     for (value in aSet)
  1536.     if (substr(value,1,1) == "!")
  1537.         nSet[substr(value,2)]
  1538.     else
  1539.         Set[value]
  1540.     return ret
  1541. }
  1542.  
  1543. # Returns true if option Opt was given on the command line.
  1544. function CmdLineOpt(Options,Opt,  i) {
  1545.     for (i = 1; (Opt,"num",i) in Options; i++)
  1546.     if (Options[Opt,"num",i] != 0)
  1547.         return 1
  1548.     return 0
  1549. }
  1550. ### End of ProcArgs library
  1551. function fmtdate(format,  cmd,ret) {
  1552.     #return strftime(format)
  1553.     cmd = sprintf("date '+%s'",format)
  1554.     cmd | getline ret
  1555.     return ret
  1556. }
  1557.